#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <asm/io.h>
#include <linux/fs.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/uaccess.h>
#define KEY_CNT 1
#define KEY_NAME "key"

#define KEY0VALUE 0xf0
#define INVAKEY   0x00

struct key_dev{
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd;
    int key_gpio;
    atomic_t keyvalue;//原子数据类型 能够保证对该变量的操作是原子性的，不会被中断或其他线程干扰
                      //避免了竞态条件和数据不一致性的问题

};
struct key_dev keydev;
static int keyio_init(void)
{
    keydev.nd = of_find_node_by_path("/key");
    if(keydev.nd==NULL)
    {
        return -EINVAL;
    }
    keydev.key_gpio = of_get_named_gpio(keydev.nd,"key-gpio",0);
    if(keydev.key_gpio < 0)
    {
 		printk("can't get key0\r\n");
		return -EINVAL;            
    }
    printk("key_gpio=%d\r\n", keydev.key_gpio);
    gpio_request(keydev.key_gpio,"key0");
    gpio_direction_input(keydev.key_gpio);
    return 0;

}
static int key_open(struct inode *inode,struct file *filp)
{
    int ret = 0;
    filp->private_data = &keydev;

    ret = keyio_init();//驱动文件打开后 先进行IO初始化
    if(ret < 0)
    {
        return ret;
    }
    return 0;
}
/*
宏定义：typedef long ssize_t  增加代码可读性
参数是由fileoperatioin的read函数参数所决定
*/
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt,loff_t *offt)
{
    int ret = 0;
    int value;
    struct key_dev *dev = filp->private_data;
    if(gpio_get_value(dev->key_gpio)==0)//按键按下
    {
        while(!gpio_get_value(dev->key_gpio));//等待按键释放
        atomic_set(&dev->keyvalue,KEY0VALUE);
    }else{
        atomic_set(&dev->keyvalue,INVAKEY);
    }
    value = atomic_read(&dev->keyvalue);//保存按键值
    ret = copy_to_user(buf,&value,sizeof(value));

    return ret;

}
static ssize_t key_write(struct file *filp,const char __user *buff,size_t cnt,loff_t *offt)
{
    return 0;
}
static int key_realease(struct inode *inode,struct file *filp)
{
    return 0;
}
static struct file_operations key_fops={
    .owner = THIS_MODULE,
    .open = key_open,
    .read = key_read,
    .write = key_write,
    .release = key_realease,
};

static int __init mykey_init(void)
{
    //初始化原子变量
    atomic_set(&keydev.keyvalue,INVAKEY);
   if(keydev.major)
   {
    keydev.devid = MKDEV(keydev.major,0);
    register_chrdev_region(keydev.devid,KEY_CNT,KEY_NAME);
   }
   else{
    alloc_chrdev_region(&keydev.devid,0,KEY_CNT,KEY_NAME);
    keydev.major = MAJOR(keydev.devid);
    keydev.minor = MINOR(keydev.devid);
   }
    keydev.cdev.owner = THIS_MODULE;
    cdev_init(&keydev.cdev, &key_fops);
    cdev_add(&keydev.cdev,keydev.devid,KEY_CNT);
    keydev.class = class_create(THIS_MODULE,KEY_NAME);
    if(IS_ERR(keydev.class))//IS_ERR检查内核函数调用的返回值
    {
        return PTR_ERR(keydev.class);//提取错误码 与IS_ERR一起用
    }

    keydev.device = device_create(keydev.class, NULL, keydev.devid, NULL, KEY_NAME);
   	if (IS_ERR(keydev.device)) {
		return PTR_ERR(keydev.device);
	} 
    return 0;
}


static void __exit mykey_exit(void)
{
    gpio_free(keydev.key_gpio);
    cdev_del(&keydev.cdev);
    unregister_chrdev_region(keydev.devid,KEY_CNT);

    device_destroy(keydev.class,keydev.devid);
    class_destroy(keydev.class);

}


module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("JiaYi");

